import { Types } from "tm";

type lang = string;

type TM_getValues = <K extends string[]>(
  data: K | Record<K[number], unknown>,
) => Record<K[number], unknown>;

type TM_setValues = (data: Record<string, unknown>) => void;

type TM_deleteValues = (keys: string[]) => void;

type TM_addValueChangeListener = (
  name: string,
  callback: Types.Vcl.cbFunction,
) => number;

type TM_removeValueChangeListener = (
  id: ReturnType<TM_addValueChangeListener>,
) => void;

type TM_getResourceURL = (name: string) => `data:${string}` | null | "";
type TM_getResourceText = (name: string) => string | null;

type TM_openInTab_O = (
  url: string,
  options?: Types.OpenTab.Option,
) => Types.OpenTab.Result;

type TM_openInTab_L = (
  url: string,
  loadInBackground?: boolean,
) => Types.OpenTab.Result;

type TM_registerMenuCommand_A = (
  name: string,
  fn: (e?: MouseEvent | KeyboardEvent) => void | Promise<void>,
  accessKey?: string,
) => number;
type TM_registerMenuCommand_O = (
  name: string,
  fn: (e?: MouseEvent | KeyboardEvent) => void | Promise<void>,
  options?: Types.Menu.Options,
) => number;
type TM_unregisterMenuCommand = (
  id: ReturnType<TM_registerMenuCommand_A>,
) => number;

type TM_xmlhttpRequest = {
  (details: Types.Xhr.Details): CM_Types.Xhr.Return;
} & Types.Xhr.Const;
type TM_download_D = (details: Types.Download.Details) => CM_Types.Xhr.Return;
type TM_download_U = (url: string, name: string) => CM_Types.Xhr.Return;

type TM_webRequest = (
  rules: Types.WebRequest.Rule[],
  listener: (
    info: "cancel" | "redirect",
    message: "ok" | "error",
    details: Types.WebRequest.Details,
  ) => void,
) => CM_Types.Xhr.Return;

type TM_addStyle = (css: string, callback?: () => void) => HTMLStyleElement;
type TM_addElement_T = <K extends keyof HTMLElementTagNameMap>(
  tagName: K,
  attributes?: Record<
    Parameters<typeof Element.prototype.setAttribute>[0],
    string | number | boolean
  >,
  callback?: () => void,
) => HTMLElementTagNameMap[K];
type TM_addElement_P = <K extends keyof HTMLElementTagNameMap>(
  parentNode: Element | ShadowRoot,
  tagName: K,
  attributes?: Record<
    Parameters<typeof Element.prototype.setAttribute>[0],
    string | number | boolean
  >,
  callback?: () => void,
) => HTMLElementTagNameMap[K];

type TM_Notification_O = (
  options: Types.Notification.Option,
  ondone?: (clicked: boolean) => void,
) => void;

type TM_Notification_T = (
  text: Types.Notification.Option["text"],
  title?: Types.Notification.Option["title"],
  image?: Types.Notification.Option["image"],
  onclick?: (ev: Event) => void,
) => void;

type TM_setClipboard = (
  data: string,
  info?: Types.ClipboardDataType | "text" | "html",
  callback?: () => void,
) => void;

type TM_cookie = {
  (
    method: Types.Cookie.Method,
    details:
      | Types.Cookie.SetDetails
      | Types.Cookie.ListDetails
      | Types.Cookie.DeleteDetails,
    callback: Types.Cookie.ListCallback | Types.Cookie.ChangeCallback,
  ): void;
  list: (
    details: Types.Cookie.ListDetails,
    callback: Types.Cookie.ListCallback,
  ) => void;
  set: (
    details: Types.Cookie.SetDetails,
    callback: Types.Cookie.ChangeCallback,
  ) => void;
  delete: (
    details: Types.Cookie.DeleteDetails,
    callback: Types.Cookie.ChangeCallback,
  ) => void;
};

interface TM_CookieObject {
  list(details: Types.Cookie.ListDetails): Promise<browser.cookies.Cookie[]>;
  set(details: Types.Cookie.SetDetails): Promise<string | void>;
  delete(details: Types.Cookie.DeleteDetails): Promise<string | void>;
}

interface TM_ScriptOverride {
  readonly merge_connects: boolean;
  readonly merge_excludes: boolean;
  readonly merge_includes: boolean;
  readonly merge_matches: boolean;
  readonly orig_connects: string[];
  readonly orig_excludes: string[];
  readonly orig_includes: string[];
  readonly orig_matches: string[];
  readonly orig_noframes: boolean | null;
  readonly orig_run_at: TM_ScriptInfo["run-at"] | null;
  readonly use_blockers: string[];
  readonly use_connects: string[];
  readonly use_excludes: string[];
  readonly use_includes: string[];
  readonly use_matches: string[];
}

interface TM_ScriptOption {
  readonly check_for_updates: boolean;
  readonly comment: string | null;
  readonly compat_foreach: boolean;
  readonly compat_metadata: boolean;
  readonly compat_powerful_this: boolean | null;
  readonly compat_prototypes?: boolean;
  readonly compat_wrappedjsobject: boolean;
  readonly compatopts_for_requires: boolean;
  readonly noframes: boolean | null;
  readonly override: TM_ScriptOverride;
  readonly run_at: TM_ScriptInfo["run-at"] | null;
  readonly sandbox: null | "raw" | "JavaScript" | "DOM";
  readonly tab_types: null | "incognito" | "normal" | "incognito+normal";
  readonly unwrap: boolean | null;
  readonly user_modified?: number | null;
}

declare interface TM_ScriptInfo {
  readonly antifeatures?: Record<string, Record<lang | "default", string>>;
  readonly author: string | null;
  readonly blockers: string[];
  readonly connects: string[];
  readonly copyright: string | null;
  readonly deleted?: number;
  readonly description: string;
  readonly description_i18n: Record<lang, string>;
  readonly downloadURL: string | null;
  readonly evilness?: number;
  readonly excludes: string[];
  readonly fileURL: string | null;
  readonly grant: string[];
  readonly header: string | null;
  readonly homepage: string | null;
  readonly icon: string | null;
  readonly icon64: string | null;
  readonly includes: string[];
  readonly lastModified: number;
  readonly matches: string[];
  readonly name: string;
  readonly name_i18n: Record<lang, string>;
  readonly namespace: string | null;
  readonly options: TM_ScriptOption;
  readonly position: number;
  readonly requires?: Record<"textContent", string>[];
  readonly resources: Types.Resource[];
  readonly "run-at":
    | `document-${"start" | "body" | "end" | "idle"}`
    | "context-menu";
  readonly supportURL: string | null;
  readonly sync: Record<"imported", number>;
  readonly system?: boolean;
  readonly unwrap: boolean | null;
  readonly updateURL: string | null;
  readonly version: string | null;
  readonly uuid?: string;
  readonly webRequest: Types.WebRequest.Rule[] | null;
}

interface TM_Info {
  readonly downloadMode: "disabled" | "browser" | "native";
  readonly isFirstPartyIsolation?: boolean;
  readonly isIncognito: boolean;
  readonly relaxedCsp: "auto" | "remove" | "yes" | "no";
  readonly sandboxMode: "js" | "raw" | "dom";
  readonly script: TM_ScriptInfo;
  readonly scriptHandler: "Tampermonkey";
  readonly scriptMetaStr: string;
  readonly scriptSource?: string;
  readonly scriptUpdateURL?: string;
  readonly scriptWillUpdate: boolean;
  readonly userAgentData?: UADataValues;
  readonly version?: string;
  toString(): "[Tampermonkey property]";
}

interface TM_Object {
  get getValue(): Promisize<CM_getValue>;
  get setValue(): Promisize<CM_setValue>;
  get listValues(): Promisize<CM_listValues>;
  get deleteValue(): Promisize<CM_deleteValue>;
  get getValues(): Promisize<TM_getValues>;
  get setValues(): Promisize<TM_setValues>;
  get deleteValues(): Promisize<TM_deleteValues>;
  get addValueChangeListener(): Promisize<TM_addValueChangeListener>;
  get removeValueChangeListener(): Promisize<TM_removeValueChangeListener>;
  get getResourceUrl(): Promisize<TM_getResourceURL>;
  get getResourceText(): Promisize<TM_getResourceText>;
  get openInTab(): Promisize<TM_openInTab_O & TM_openInTab_L>;
  get getTab(): () => AnyRecord;
  get saveTab(): Promisize<CM_saveTab>;
  get getTabs(): () => Record<number, AnyRecord>;
  get registerMenuCommand(): Promisize<
    TM_registerMenuCommand_A & TM_registerMenuCommand_O
  >;
  get unregisterMenuCommand(): Promisize<TM_unregisterMenuCommand>;
  get xmlHttpRequest(): (details: Types.Xhr.Details) => Types.Xhr.PromiseReturn;
  get webRequest(): TM_webRequest;
  get download(): PromisizeReturn<TM_download_D, Record<string, never>>;
  get addStyle(): Promisize<TM_addStyle>;
  get addElement(): Promisize<TM_addElement_T>;
  get notification(): PromisizeReturn<
    TM_Notification_O & TM_Notification_T,
    boolean
  >;
  get setClipboard(): TM_setClipboard;
  get log(): Promisize<typeof console.log>;
  get cookie(): TM_CookieObject;
  get info(): Omit<TM_Info, "toString">;
  toString(): "[Tampermonkey property]";
}

interface TM_SafeWindow extends Window {
  close(): void;
  focus(): void;
  onurlchange?: ((info: Record<"url", string>) => void) | null;
  external: External & {
    readonly Tampermonkey?: {
      getVersion: (
        callback: (info: { id: string; version: TM_Info["version"] }) => void,
      ) => void;
      isInstalled: (
        name: TM_ScriptInfo["name"],
        namespace: TM_ScriptInfo["namespace"],
        callback: (info: {
          enable: boolean | undefined;
          installed: boolean;
          name: TM_ScriptInfo["name"];
          version: TM_ScriptInfo["version"] | undefined;
        }) => void,
      ) => void;
      openOptions: (hash: `#${string}`, callback: () => void) => void;
    };
  };
}

declare module "tm" {
  export const GM: TM_Object;
  export const GM_getValue: CM_getValue;
  export const GM_setValue: CM_setValue;
  export const GM_listValues: CM_listValues;
  export const GM_deleteValue: CM_deleteValue;
  export const GM_getValues: TM_getValues;
  export const GM_setValues: TM_setValues;
  export const GM_deleteValues: TM_deleteValues;
  export const GM_addValueChangeListener: TM_addValueChangeListener;
  export const GM_removeValueChangeListener: TM_removeValueChangeListener;
  export const GM_getResourceURL: TM_getResourceURL;
  export const GM_getResourceText: TM_getResourceText;
  export const GM_openInTab: TM_openInTab_O & TM_openInTab_L;
  export const GM_getTab: CM_getTab;
  export const GM_saveTab: CM_saveTab;
  export const GM_getTabs: CM_getTabs;
  export const GM_registerMenuCommand: TM_registerMenuCommand_A &
    TM_registerMenuCommand_O;
  export const GM_unregisterMenuCommand: TM_unregisterMenuCommand;
  export const GM_xmlhttpRequest: TM_xmlhttpRequest;
  export const GM_download: TM_download_D & TM_download_U;
  export const GM_webRequest: TM_webRequest;
  export const GM_addStyle: TM_addStyle;
  export const GM_addElement: TM_addElement_T & TM_addElement_P;
  export const GM_notification: TM_Notification_O & TM_Notification_T;
  export const GM_setClipboard: TM_setClipboard;
  export const GM_log: typeof console.log;
  export const GM_cookie: TM_cookie;
  export const GM_info: TM_Info;
  export let unsafeWindow: unsafeWindow;
}
